package com.ldn.starter.advice;

import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.UnexpectedTypeException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.IbatisException;
import org.springframework.core.MethodParameter;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import com.ldn.basic.dispose.exception.AppException;
import com.ldn.basic.dispose.exception.LoginException;
import com.ldn.basic.dispose.exception.UpdateException;
import com.ldn.basic.dispose.vo.ResultDataVo;
import com.ldn.basic.dispose.vo.WechatPayNotifyVo;
import com.ldn.basic.enums.ReturnCodeEnums;

import java.sql.SQLException;
import java.util.Objects;

/**
 * @author 李大牛家的牛 (scafel@163.com)
 * @createDate 2024/7/9 - 16:35
 * @description 全局数据返回处理类
 **/
@Slf4j
@RestControllerAdvice("com.ldn.controller.api")
public class MyResponseAdvice implements ResponseBodyAdvice<Object> {
    private final ObjectMapper objectMapper;

    public MyResponseAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supports(
            @NotNull MethodParameter returnType,
            @NotNull Class<? extends HttpMessageConverter<?>> converterType)
    {
        return true;
    }

    @Override
    public Object beforeBodyWrite(
            Object body,
            @NotNull MethodParameter returnType,
            @NotNull MediaType selectedContentType,
            @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
            @NotNull ServerHttpRequest request,
            @NotNull ServerHttpResponse response
    ) {
//        log.info("beforeBodyWrite: body =>{}", body);
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(ResultDataVo.success(body));
            } catch (JsonProcessingException ex) {
                return ResultDataVo.fail(ReturnCodeEnums.RC500.getCode(), ReturnCodeEnums.RC500.getMessage());
            }
        } else if (body instanceof ResultDataVo) {
            return body;
        } else if (body instanceof WechatPayNotifyVo) {
            return body;
        } else {
            return ResultDataVo.success(body);
        }
    }

    @ExceptionHandler(LoginException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> loginException(LoginException ex) {
        log.error("loginException:code =>{} ex =>{}", ex.getCode(), ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC201.getCode(), ex.getMessage());
    }

    @ExceptionHandler(AppException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> appException(AppException ex) {
        log.error("appException: code =>{} ex =>{}", ex.getCode(), ex.getMessage());
        return ResultDataVo.fail(ex.getCode(), ex.getMessage());
    }

    @ExceptionHandler({MethodArgumentNotValidException.class, HttpMediaTypeNotSupportedException.class})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.error("handleMethodArgumentNotValidException: ex =>{}", ex.getMessage());
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder(ReturnCodeEnums.RC400.getMessage()).append(",");
        FieldError fieldError = bindingResult.getFieldErrors().get(0);
        sb.append(Objects.requireNonNull(fieldError.getDefaultMessage()));
        String msg = sb.toString();
        return ResultDataVo.fail(ReturnCodeEnums.RC400.getCode(), msg);
    }

    @ExceptionHandler(SQLException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> sqlException(SQLException ex) {
        log.error("SQLException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC600.getCode(), ReturnCodeEnums.RC600.getMessage());
    }

    @ExceptionHandler(IbatisException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> ibatisException(IbatisException ex) {
        log.error("IbatisException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC600.getCode(), ReturnCodeEnums.RC600.getMessage());
    }

    @ExceptionHandler(DataAccessException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> dataAccessException(DataAccessException ex) {
        log.error("dataAccessException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC600.getCode(), ReturnCodeEnums.RC600.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> exception(Exception ex) {
        log.error("exception: ex =>{}", ex.getMessage());
        log.error("exception: name =>{}", ex.getClass().getName());
        return ResultDataVo.fail(ReturnCodeEnums.RC500.getCode(), ReturnCodeEnums.RC500.getMessage() );
    }

    @ExceptionHandler(UpdateException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> exception(UpdateException ex) {
        log.error("exception: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC204.getCode(), ReturnCodeEnums.RC204.getMessage() );
    }

    @ExceptionHandler(SaTokenException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> saTokenException(SaTokenException ex) {
        log.error("saTokenException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC201.getCode(), ReturnCodeEnums.RC201.getMessage());
    }

    @ExceptionHandler(NoResourceFoundException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultDataVo<Object> noResourceFoundException(NoResourceFoundException ex) {
        log.error("noResourceFoundException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC404.getCode(), ReturnCodeEnums.RC404.getMessage());
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    @ResponseBody
    public ResultDataVo<Object> bindExceptionHandler(BindException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder(ReturnCodeEnums.RC404.getMessage());
        FieldError fieldError = bindingResult.getFieldErrors().get(0);
        sb.append(fieldError.getField()).append(":").append(Objects.requireNonNull(fieldError.getDefaultMessage()));
        String msg = sb.toString();
        log.error("bindExceptionHandler: ex =>{}", msg);
        return ResultDataVo.fail(ReturnCodeEnums.RC404.getCode(), ReturnCodeEnums.RC404.getMessage());
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({UnexpectedTypeException.class, ConstraintViolationException.class})
    @ResponseBody
    public ResultDataVo<Object> ortherException(Exception ex) {
        log.error("ortherException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC400.getCode(), ReturnCodeEnums.RC400.getMessage());
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseBody
    public ResultDataVo<Object> httpMessageNotReadableException(HttpMessageNotReadableException ex) {
        log.error("httpMessageNotReadableException: ex =>{}", ex.getMessage());
        return ResultDataVo.fail(ReturnCodeEnums.RC203.getCode(), ReturnCodeEnums.RC203.getMessage());
    }

}
